use std::marker::PhantomData;

use bits::{BitsOps, IntoBits};

pub mod inst_type {

    pub struct R;
    pub struct I;
    pub struct S;
    pub struct B;
    pub struct U;
    pub struct J;
    pub mod field {
        use super::{B, I, J, R, S};

        pub trait Rd {}
        impl Rd for R {}
        impl Rd for I {}
        impl Rd for J {}
        pub trait Funct3 {}
        impl Funct3 for R {}
        impl Funct3 for I {}
        impl Funct3 for S {}
        impl Funct3 for B {}

        pub trait Rs1 {}
        impl Rs1 for R {}
        impl Rs1 for I {}
        impl Rs1 for S {}
        impl Rs1 for B {}

        pub trait Rs2 {}
        impl Rs2 for R {}
        impl Rs2 for S {}
        impl Rs2 for B {}
    }
}

pub struct Inst<T> {
    pub raw: u32,
    phantom: PhantomData<T>,
}

impl<T> Inst<T> {
    pub fn opcode(&self) -> u32 {
        self.raw.bits(0..=6).read()
    }
}
impl<T: inst_type::field::Rd> Inst<T> {
    pub fn rd(&self) -> u32 {
        self.raw.bits(7..=11).read()
    }
}
impl<T: inst_type::field::Funct3> Inst<T> {
    pub fn funct3(&self) -> u32 {
        self.raw.bits(12..=14).read()
    }
}
pub enum InstFmt {
    R(Inst<inst_type::R>),
    I(Inst<inst_type::I>),
    S(Inst<inst_type::S>),
    B(Inst<inst_type::B>),
    U(Inst<inst_type::U>),
    J(Inst<inst_type::J>),
}

pub enum Opcode {
    Load,
    LoadFp,
    Custom0,
    OpImm,
    Auipc,
    OpImm32,
    Store,
    StoreFp,
    Custom1,
    Amo,
    Op,
    Lui,
    Op32,
    MADD,
    MSUB,
    NMSUB,
    NMADD,
    OpFp,
}
